package com.aelitis.azureus.core.speedmanager.impl.v2;

import org.gudy.azureus2.core3.util.SystemTime;

/**
 * Created on Jun 29, 2007 Created by Alan Snyder Copyright (C) Azureus Software, Inc, All Rights Reserved.
 * <p/>
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

public class PingSpaceMonitor {

    PingSpaceMapper pingMap;
    long startTime;
    private static final long INTERVAL = 1000 * 60 * 5L; // five min interval.

    int maxGoodPing;
    int minBadPing;

    // ping counters
    int nBadPings, nGoodPings, nNeutralPings = 0;

    // saturated mode counters
    int upNone, upLow, upMed, upHigh, upAtLimit = 0;
    int downNone, downLow, downMed, downHigh, downAtLimit = 0;

    TransferMode transferMode;

    // To get new limit back to application
    public static final int UPLOAD = 88;
    public static final int DOWNLOAD = 89;
    public static final int NONE = 0;

    boolean hasNewLimit = false;
    int newLimit = -2;
    int limitType = NONE;

    public PingSpaceMonitor(int _maxGoodPing, int _minBadPing, TransferMode mode) {
        maxGoodPing = _maxGoodPing;
        minBadPing = _minBadPing;

        reset(mode);
    }

    /**
     * 
     * @param downRate
     *            -
     * @param upRate
     *            -
     */
    public void setCurrentTransferRates(int downRate, int upRate) {
        pingMap.setCurrentTransferRates(downRate, upRate);
    }

    /**
     * Do a check and decide if the limits should be dropped.
     * 
     * @param lastMetricValue
     *            -
     * @param mode
     *            - the TransferMode
     * @return - true if the limits should be dropped.
     */
    public boolean addToPingMapData(int lastMetricValue, TransferMode mode) {

        // reset if we are changing modes.
        if (transferMode == null) {
            transferMode = mode;
        }

        // reset everything if we change modes.
        if (!transferMode.equals(mode)) {
            reset(mode);
            transferMode = mode;
            return false;
        }

        transferMode = mode;

        if (lastMetricValue < maxGoodPing) {
            nGoodPings++;
        } else if (lastMetricValue > minBadPing) {
            nBadPings++;
        } else {
            nNeutralPings++;
        }

        pingMap.addMetricToMap(lastMetricValue);

        // If the interval is up, we need to either recommend new limits or reset.
        long curr = SystemTime.getCurrentTime();

        if (curr > startTime + INTERVAL) {

            boolean needLowerLimts = checkForLowerLimits();
            if (needLowerLimts) {

                // prepare the package for lowering the limit.
                if (transferMode.isConfTestingLimits()) {
                    reset(mode);
                    return false;
                } else if (transferMode.isDownloadMode()) {

                    // recommend a new downloading limit.
                    newLimit = pingMap.guessDownloadLimit();

                    SpeedManagerLogger.trace("PingSpaceMonitor -> guessDownloadLimit: newLimit=" + newLimit);

                    // on cable modems uploads can be over-estimated by 3x.
                    int uploadLimitGuess = pingMap.guessUploadLimit();
                    SpeedManagerLogger.trace("PingSpaceMonitor -> guessUploadLimit: guessUploadLimit=" + uploadLimitGuess);

                    // download limit cannot be less the 40k
                    if (newLimit < 40960) {
                        newLimit = 40960;
                    }

                    hasNewLimit = true;
                    limitType = DOWNLOAD;
                    reset(mode);
                    return true;
                } else {

                    // only seeding mode is left recommend a new upload limit.
                    newLimit = pingMap.guessUploadLimit();

                    // upload limit cannot be less the 20k
                    if (newLimit < 20480) {
                        newLimit = 20480;
                    }

                    hasNewLimit = true;
                    limitType = UPLOAD;
                    reset(mode);
                    return true;
                }
            } else {
                // No need for lower limits.
                reset(mode);
            }
        }// if

        return false;
    }

    // Simple test currently. 15% bad pings.
    private boolean checkForLowerLimits() {
        int totalPings = nGoodPings + nBadPings + nNeutralPings;

        float percentBad = (float) nBadPings / (float) totalPings;

        if (percentBad > 0.15f) {
            return true;
        } else {
            return false;
        }

    }

    public void reset(TransferMode mode) {

        // log results.
        StringBuffer sb = new StringBuffer("ping-monitor:");
        sb.append("good=").append(nGoodPings).append(":");
        sb.append("bad=").append(nBadPings).append(":");
        sb.append("neutral=").append(nNeutralPings);

        SpeedManagerLogger.log(sb.toString());

        // reset all the counters.
        nBadPings = nGoodPings = nNeutralPings = 0;

        // saturated mode counters
        upNone = upLow = upMed = upHigh = upAtLimit = 0;
        downNone = downLow = downMed = downHigh = downAtLimit = 0;

        pingMap = new PingSpaceMapper(maxGoodPing, minBadPing);
        startTime = SystemTime.getCurrentTime();

        transferMode = mode;
    }

    /**
     * True if we have a new limit.
     * 
     * @return - true if there is a new limit.
     */
    boolean hasNewLimit() {
        return hasNewLimit;
    }

    public int getNewLimit() {
        return newLimit;
    }

    public int limitType() {
        return limitType;
    }

    /**
     * Call after getting new limits.
     */
    public void resetNewLimit() {
        hasNewLimit = false;
        newLimit = -2;
        limitType = NONE;
    }

}// class
